#include "GameControl.h"
#include "../draw/DrawSystem.h"
#include "../control/ControlSystem.h"
#include "../tools/Log.h"
#include "../tools/xml/ticpp.h"
#include "../tools/CollisionDetection.h"
#include "commands/Unit_Move.h"

#include "UnitType.h"

extern const unsigned long SUBSPACE_RESOLUTION;

static float cameraSpeedPerMillisecond = SUBSPACE_RESOLUTION / 100;
static float cameraRotateSpeedPerMillisecond = 3.1415926 / 1000;
static float cameraBackupAmount = SUBSPACE_RESOLUTION;

Game* Game::thisInstance = NULL;

Game::Game() : InputInterface(), selected(), mouseDownX(-1), mouseDownY(-1), gui(), map()
{
	selectionRect.drawPos.z = DrawSystem::FOREGROUND;
	selectionRect.DisableDraw();
	thisInstance = this;
	
	map.load_from_file("conf/map.conf");
	gui.load("conf/hud.xml");
	gui.enable();
	InputSystem::Instance().setFocus(this);
}

Game::~Game()
{
	gui.disable();
	gui.kill();
	for(UnitMap::iterator itr = units.begin(); itr != units.end(); itr ++)
		delete itr->second;
	
	units.clear();
}

void Game::init(const string game_config_file, const GameConf&)
{
	using namespace ticpp;
	
	try	{
		TiXmlDocument doc(game_config_file);
		doc.LoadFile();
		
		TiXmlElement* pElem = doc.FirstChildElement();
		
		string hud = pElem->Attribute("hud");
		UnitType::load_from_file(pElem->Attribute("unit_types"));
		string building_types = pElem->Attribute("building_types");
		string resource_types = pElem->Attribute("resource_types");
		
	} catch( Exception& ex ) {
		lout << "WARNING: Game - Error parsing \"" << game_config_file << "\": " << ex.what() << endl;
	}
}

// dt is the time (in ms) since the last frame
void Game::step(unsigned dt)
{
	for(UnitMap::iterator itr = units.begin(); itr != units.end(); itr ++)
		itr->second->update(dt);
}

void Game::init_game(unsigned long seed)
{
	srand(seed);
	
	// Place initial units, buildings
}

void Game::create_unit(char type[], unsigned x, unsigned y)
{
	if(x >= map.width() || y >= map.height()) {
		lout << "Error: Attempted to create unit outside of map range." << endl;
		return;
	}
	
	UnitType* ut = UnitType::find_unit_type_by_name(type);
	
	if(ut == NULL) {
		lout << "Error: Attempted to create an invalid unit type." << endl;
		return;
	}
	
	Unit* new_unit = new Unit(ut, x, y);
	units[new_unit->id] = new_unit;
	
	lout << "Created unit (" << new_unit->id << ")" << endl;
}

void Game::move_unit(unsigned unit_id, unsigned x, unsigned y)
{
	if (units.find(unit_id) != units.end())
		units[unit_id]->move(lVec3D(x,y, map.getHeight(x,y)));
	else
		lout << "Attempted to move unit that doesn't exist." << endl;
}

void Game::pauseGame() {
	GameControl::Instance()->pauseGame();
}
void Game::resumeGame() {
	GameControl::Instance()->resumeGame();
	InputSystem::Instance().setFocus(this);
}

void Game::select_point(const lVec3D& where) {
	selected.clear();
	
	fVec2D a, b, c, d;
	
	for(UnitMap::iterator i = units.begin(); i != units.end(); i++) {
		i->second->getBoundingBox(a, b, c, d);
		double angle = i->second->orientation.z * 3.1415926 / 180;
		if(PointRectangleCollision(convert(where), angle, a, b, c, d)) {
			selected.push_back(i->second);
			return;
		}
	}
	
}

void Game::select_range(const fVec2D &A, const fVec2D &B, const fVec2D &C, const fVec2D &D) {
	selected.clear();
//	fVec2D AD = D - A;
//	double ang = atan2(AD.y, AD.x);
	
	for(UnitMap::iterator i = units.begin(); i != units.end(); i++) {
		lVec3D intersect;
		if(RayTriangleCollision(i->second->getDrawPos(), fVec3D(0,0,1), convert(A), convert(B), convert(C), intersect) || 
		   RayTriangleCollision(i->second->getDrawPos(), fVec3D(0,0,1), convert(A), convert(C), convert(D), intersect) ) {
			selected.push_back(i->second);
		}
	}
}

void Game::onMouseButtonDown(KEY_TYPE k, int x, int y)
{
	if(k == MOUSE_BUTTON_LEFT) {
		if(gui.onMouseButtonDown(k,x,y))
			return;
	
		mouseDownX = x;
		mouseDownY = y;
	} else if (k == MOUSE_BUTTON_RIGHT) {
		lVec3D pos;
		if(traceMousePos(x, y, pos)) {
			for(vector<GameObject*>::iterator i = selected.begin(); i != selected.end(); i++) {
				GameControl::Instance()->appendGameCommand(new Unit_Move((*i)->id, pos.x, pos.y));
			}
		}
	}
}

void Game::onMouseButtonUp(KEY_TYPE k, int x, int y) {
	if(k == MOUSE_BUTTON_LEFT && mouseDownX != -1 && mouseDownY != -1) {
		if(selectionRect.drawEnabled)
			selectionRect.DisableDraw();
	
//		if(units.empty())	{	mouseDownX = -1;	mouseDownY = -1;	return; }
		
		
		if(mouseDownX == x && mouseDownY == y) {
			lVec3D pos;
			if(traceMousePos(x, y, pos))
				select_point(pos);
		} else {
			lVec3D A, B, C, D;
			
			if(	traceMousePos(mouseDownX, mouseDownY, A) && traceMousePos(mouseDownX, y, B) &&
				traceMousePos(x, y, C) && traceMousePos(x, mouseDownY, D)) {
				select_range(convert(A), convert(B), convert(C), convert(D));
			}
		}
	
		if(!selected.empty()) {
			lout<<"Selected object(s)";
			for(vector<GameObject*>::iterator i = selected.begin(); i != selected.end(); i++) {
				lout<<" id #"<<(*i)->id;
			}
			lout<<endl;
		}
	
		mouseDownX = -1;
		mouseDownY = -1;
	}
}

void Game::onMouseWheelUp() {
	DrawSystem::Instance().cameraPosition -= DrawSystem::Instance().cameraLookAt.normalized() * cameraBackupAmount;
}
void Game::onMouseWheelDown() {
	DrawSystem::Instance().cameraPosition += DrawSystem::Instance().cameraLookAt.normalized() * cameraBackupAmount;
}

void Game::onKeyDown(KEY_TYPE button)
{
	gui.onKeyDown(button);
	
	if(button == KEY_ESC)
	{	GameControl::Instance()->pauseGame();	}

	switch(button) {
		case 'W':
			DrawSystem::Instance().cameraPosSpeed.x += cameraSpeedPerMillisecond;	break;
		case 'S':
			DrawSystem::Instance().cameraPosSpeed.x -= cameraSpeedPerMillisecond;	break;
		case 'A':
			DrawSystem::Instance().cameraPosSpeed.y -= cameraSpeedPerMillisecond;	break;
		case 'D':
			DrawSystem::Instance().cameraPosSpeed.y += cameraSpeedPerMillisecond;	break;
		case 'Q':
			DrawSystem::Instance().cameraRotSpeed.y += cameraRotateSpeedPerMillisecond;	break;
		case 'E':
			DrawSystem::Instance().cameraRotSpeed.y -= cameraRotateSpeedPerMillisecond;	break;
	}
	
}
void Game::onKeyUp(KEY_TYPE button) {
	gui.onKeyUp(button);
	switch(button) {
		case 'W':
			DrawSystem::Instance().cameraPosSpeed.x -= cameraSpeedPerMillisecond;	break;
		case 'S':
			DrawSystem::Instance().cameraPosSpeed.x += cameraSpeedPerMillisecond;	break;
		case 'A':
			DrawSystem::Instance().cameraPosSpeed.y += cameraSpeedPerMillisecond;	break;
		case 'D':
			DrawSystem::Instance().cameraPosSpeed.y -= cameraSpeedPerMillisecond;	break;
		case 'Q':
			DrawSystem::Instance().cameraRotSpeed.y -= cameraRotateSpeedPerMillisecond;	break;
		case 'E':
			DrawSystem::Instance().cameraRotSpeed.y += cameraRotateSpeedPerMillisecond;	break;
	
	}
}

void Game::onMouseMove(int x, int y) {
	if(mouseDownX != -1 && mouseDownY != -1) {
		if(selectionRect.drawEnabled == false)
			selectionRect.EnableDraw();
		
		double minX = min(x, mouseDownX);
		double maxX = max(x, mouseDownX);
		double minY = min(y, mouseDownY);
		double maxY = max(y, mouseDownY);
		
		double heightRatio = DrawSystem::Instance().getFrameHeight() / DrawSystem::Instance().getWindowHeight();
		double widthRatio = DrawSystem::Instance().getFrameWidth() / DrawSystem::Instance().getWindowWidth();
		
		minX *= widthRatio;
		maxX *= widthRatio;
		minY *= heightRatio;
		maxY *= heightRatio;
	
		selectionRect.drawPos.x = minX;
		selectionRect.drawPos.y = minY;
		selectionRect.dimensions.x = maxX - minX;
		selectionRect.dimensions.y = maxY - minY;	
	}
}

bool Game:: traceMousePos(int x, int y, lVec3D& where) {
	fVec3D &camPos = DrawSystem::Instance().cameraPosition;
	fVec3D &camLook = DrawSystem::Instance().cameraLookAt;
	fVec3D pos(camPos), dir(camLook);
	
	dir.normalize();
	double FOV = DrawSystem::Instance().getFOV() * 3.1415926 / 180 / 2;

	int height = DrawSystem::Instance().getWindowHeight();
	int width = DrawSystem::Instance().getWindowWidth();
	double yRatio = (y - height / 2.0) / height * 2;
	double xRatio = -(x - width / 2.0) / height * 2;
	
	double normZAng = FOV;
	
	double cy = std::cos(normZAng), cz = 0, sy = std::sin(normZAng), sz = 0;
	
	fVec3D vertPt(cy, 0, sy);
	vertPt *= 1 / vertPt.x;
	vertPt.x = 0;
	
	fVec3D horizPt(0, vertPt.z, 0);
	fVec3D pt = fVec3D(1,0,0) + (horizPt*xRatio) + (vertPt*yRatio);
	
	double zAng = std::asin(dir.z);
	double xyAng = std::atan2(-dir.y, dir.x);

	cy = std::cos(zAng); cz = std::cos(xyAng); sy = std::sin(zAng); sz = std::sin(xyAng);
	
	dir = fVec3D(pt.x*cz*cy + pt.y*sz + pt.z*(-sy)*cz, pt.x*(-sz)*cy + pt.y*cz + pt.z*sy*sz, pt.x*sy + pt.z*cy);

	dir.normalize();

	return map.traceRayToMap(pos, dir, where);
}

void Game::onWindowResize(float W, float H) {
	gui.onWindowResize(W,H);
}
